import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;
import java.util.Random;

public class Colonia implements Observer{
	
	protected static final double A = 0.1;
	
	 	protected Grafo grafo;
	    protected ArrayList<Formiga> formigas;
	    protected int      numeroFormigas;
	    protected int      contadorFormigas;
	    protected int      contadorIteracao;
	    protected int      numeroIteracoes;
	    
	    protected PrintStream saida;
	    
	    private int  numColonia;
	    
	    private static int contadorColonia = 0;
	    
	    
	    public ArrayList<Formiga> getFormigas() {
			return formigas;
		}

		public void setFormigas(ArrayList<Formiga> formigas) {
			this.formigas = formigas;
		}


		public int getNumeroIteracoes() {
			return numeroIteracoes;
		}

		public void setNumeroIteracoes(int numeroIteracoes) {
			this.numeroIteracoes = numeroIteracoes;
		}

		public int getNumColonia() {
			return numColonia;
		}

		public void setNumColonia(int numColonia) {
			this.numColonia = numColonia;
		}

		public synchronized void start()
	    {
	    	formigas = gerarFormigas(grafo, numeroFormigas);
	    	contadorIteracao = 0;
	    	
	    	 try
	         {
	             saida = new PrintStream(new FileOutputStream("/home/david/testes/" + getGrafo().getP().getNome() +  numColonia + "_" + grafo.getNumCidades() + "x" + formigas.size() + "x" + numeroIteracoes + "_colony.txt"));
	         }
	         catch(Exception ex)
	         {
	             ex.printStackTrace();
	         }
	    	
	    	while(contadorIteracao < numeroIteracoes){
	    	    iteracao();
	            try
	            {
	                wait();
	            }
	            catch(InterruptedException ex)
	            {
	                ex.printStackTrace();
	            }
	            
	            // Sincroniza acesso ao grafo
	            synchronized(grafo)
	            {
	                // Atualiza o feromoneo global
	                atualizaFeromoneoGlobal();
	            }
	    	}
	    	 if(contadorIteracao == numeroIteracoes)
	         {
	             saida.close();
	         }
	    }
	    
	    private void iteracao()
	    {
	        contadorFormigas = 0;
	        contadorIteracao++;
	        
	        for(Formiga f:formigas){
	        	f.start();
	        }

	    }
	    
	    public Grafo getGrafo(){
	    	return grafo;
	    }
	        
	    public Colonia(Grafo pGrafo, int pNumeroFormigas, int pNumeroIteracoes)
	    {
	    	grafo = pGrafo;
	    	numeroFormigas = pNumeroFormigas;
	    	numeroIteracoes = pNumeroIteracoes;
	    	contadorColonia++;
	    	numColonia = contadorColonia;
	    }
	    
	    protected ArrayList<Formiga> gerarFormigas(Grafo pGrafo, int pNumFormigas)
	    {
	        Random ran = new Random(System.currentTimeMillis());
	        Formiga.reseta();
	        Formiga.setColonia(this);
	        ArrayList<Formiga> formigas = new ArrayList<Formiga>();
	        for(int i = 0; i < pNumFormigas; i++)
	        {
	        	Formiga f = new Formiga(grafo.getCidades().get((int)(grafo.getNumCidades() * ran.nextDouble())), this);
	            formigas.add(f);
	        }
	        
	        return formigas;
	    }
   
	    public synchronized void update(Observable ant, Object obj)
	    {
	        contadorFormigas++;
	        
	        if(contadorFormigas == formigas.size())
	        {
	            saida.println(";" + Formiga.getStringMELHOR_ROTA_GLOBAL() + ";" + grafo.getMediaAtracao()); 
	            notify();
	        }
	        
	    }
	    
	    protected void atualizaFeromoneoGlobal()
	    {
	        double evaporacao = 0;
	        double deposito  = 0;
	        
	        for(int r = 0; r < grafo.getNumCidades(); r++)
	        {
	            for(int s = 0; s < grafo.getNumCidades(); s++)
	            {
	                if(r != s)
	                {
	                    // Soma das atrações
	                    double somaAtracao = 
	                        ((double)1 / Formiga.VALOR_MELHOR_ROTA_GLOBAL);
	                                    
	                    // gEvaporação
	                    evaporacao = ((double)1 - A) * grafo.getAtracao(grafo.getCidades().get(r).getNum(),grafo.getCidades().get(s).getNum());
	                    // Deposito de feromoneo
	                    deposito  = A * somaAtracao;
	                    
	                    // Update Atração
	                    grafo.atualizaAtracao(r, s, evaporacao + deposito);
	                }
	            }
	        }
	    }

	    public double getValorMelhorRota()
	    {
	        return Formiga.VALOR_MELHOR_ROTA_GLOBAL;
	    }
	    
	    public int getMelhorIteracao()
	    {
	        return Formiga.NUMERO_MELHOR_ITERACAO;
	    }

		public int getContadorIteracao() {
			return contadorIteracao;
		}

		public void setContadorIteracao(int contadorIteracao) {
			this.contadorIteracao = contadorIteracao;
		}
		
		public boolean fim()
	    {
	        return contadorIteracao == numeroIteracoes;
	    }
	    

}
